查看原文
其他

[爱琴海]2017 最新 CTF Demo 试玩版题目 ------WriteUp

2017-07-19 NearJMP 看雪学院

爱琴海 Demo 地址:

http://bbs.pediy.com/thread-219212.htm

下载题目,直接IDA打开查看字符串, 发现有很多py开头的函数,并且还有GetProcessAddress失败的提示信息,看来确实是Demo试玩版,提示信息很足,所以推测应该是内置了python脚本运行的。

  • 由于之前没搞过python的,直接百度下相关内容了解到以下两个信息

    • 如果只是单独的python脚本行,可以使用PyRun_SimpleString

    • 如果是脚本源文件,需要先编译再调用,也可以直接加载编译后的pyc使用PyEval_EvalCode调用

  • 了解以上两个信息后,打开OD调试,在IDA中通过交叉引用找到GetProcessAddress后的函数地址的call调用处 

    对其下断点,运行发现断不下来!!!

OD中ctrl+F2重新运行,发现了个奇怪的现象,之前的程序窗口没有被关闭,就又开了一个新进程。于是,关掉所有crackme, 打开任务管理器,重新运行cm,看到出现了两个进程

  • 在IDA导入表中查找CrateProcess, 可以看到程序使用的是CreateProcessW, OD中使用bp CreateProcessW对其下断运行

这里发现没有额外的命令行参数,并且在IDA中可以看到,CreateProcess之后,进程1就在WaitForSingleObject了,所以python相关的代码应该全部在进程2中执行,那么怎么调试进程2呢?

由于进程1没有使用调试模式来创建进程2,所以在进程2被创建成功后,使用od附加是可以成功附加的,但是这个时候附加已经过了python脚本的加载时机了,需要更早一些才行,所以我这里采用修改CC来触发异常给调试器接管。 

考虑到GetProcessAddress之用后这些函数才正式开始使用,所以我找的是执行了GetProcessAddress函数之后的sub_403E10函数,这个函数里可以看到有Py_SetProgramName,Py_Initialize等初始化设置的函数,所以我选择在sub_403E10处修改0x56为0xCC     

修改完成后直接运行CM,触发CC直接断在进程2的0x403E10处要设置OD为默认的实时调试器

单步跟看到这样一个路径0022CE90 00416140 UNICODE "C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\_MEI33682\base_"找过去看了下,是一些python的运行环境,并没有关键的东西,继续跟代码。

这里添加了__main__,看来下面就快要到调用__main__函数的地方了, 继续跟代码, 就在下方不远处有一个小循环

这个一直在查找[ebx+0x11]处等于0x73的结果,查看EBX对应的地址发现有这样一个表

简单推测可知道开头4个字节为每一项的长度,0x11偏移处为字符串起始,0x73是s, 以s开头的有spyiboot01_bootstrap和 sKERNEL32.dll 

找到一个后就去打开自身,从自身0x1F000 + offset处读取数据,解压数据,执行。最终分析的这个表的结构是|长度|偏移|大小|解压后大小|名字| 解压后的数据就是pyc,只不过被抹去了前12字节,抹去的前12字节主要的是前4字节Magic Number

打开自身

读取padding信息(数据为压缩过的)

解压使用的是zlib库,版本1.2.8,可根据代码中的错误提示字符串搜索得到。 

C代码的执行的逻辑到这就结束了,因为程序运行起来看到的界面都是在python中执行的,后面就说下怎么获取并反编译pyc     

前面说到了一个表,这个表在原始的cm文件的末尾,可以直接拷贝出来单独保存为dataInfo,然后用下面的脚本解出所有的pyc

解压出来的pyc中主要的是sKERNEL32.dll.pyc 下载 Easy Python Decompiler v1.3.2 参考这篇http://o1o1o1o1o.blogspot.hk/2016/11/python-pyinstaller-reverse-engineer.html 中的说法

下面這包是用 python 3.4,一樣可以查表 
或是利用 archive_viewer.py 顯示前幾包的內容,開頭的 4 bytes 就是 magic number 
'EE 0C 0D 0A'    

在sKERNEL32.dll.pyc最前面添加 EE 0C 0D 0A XX XX XX XX XX XX XX XX 十二个字节,注意是添加不是修改 我一开始是修改了开头的12字节,结果导致Easy Python Decompiler无法还原,后来在这个帖子里 
https://stackoverflow.com/questions/21067313/how-convert-and-save-python-module-from-pyobject-as-binary-data-to-use-it-lat 看到一段代码

这里减8 是因为是python2.7 只有前8字节是magic value, 而3.4是前12字节。

补上12字节的magic value后直接拖到Easy Python Decompiler就能看到python代码了,如下

代码看起来有些问题,先注释掉while后面的用python运行下看看,注意运行环境是python3.x

看到了这个界面,看来这就是验证的地方,仔细阅读下while循环中的代码,得知z4是输入且长度为10,z1用来验证输入的有效性, 

可知key的长度为10且由0-9,a-f组成,str(int(z4, 16))用来把输入的16进制串转为10进制串。   

关键的判断点是if hashlib.md5(z3.encode('utf-8')).hexdigest() == b(m5, 239)     

所以算法就是判断输入的值转10进制字符串后的md5是否与预设值相等,将b(m5, 239)放在前面直接print得到md5为524b38df7fe44db9f9b6621f14550e55

但是这个md5值与实际执行时的并不一样。

代码中其实还有smc, 最终运行的python脚本与文件里保存的有一点区别,修改代码的函数是sub_407CC0

这里有个有趣的事情,下面这段代码如果单步运行,那么最后不会修改python代码,直接走free,如果在这段代码之后下断点直接run过去,就会多走修改python代码的处理,经过几次单步感觉是pop ss影响了push fd的执行,noppush sspop ss就能单步跟到smc处

smc代码

所以最终修改后的代码应该在0x1c5处比原始的小 1 ,用比较软件打开内存dump的代码和文件中解压出来的代码可以看到确实只有此处是不同的 

最终运行的脚本m5数组应该是

cmd5查到这个md5 424b38df7fe44db9f9b6621f14550e55 对应的数为597480335089 转为十六进制小写就是最终的key:8b1c9a66f1

感谢 风间仁 大神指出错误!


本文由 看雪论坛 NearJMP 原创,转载请注明来自看雪社区

如果你喜欢的话,不要忘记点个赞哦!



热门阅读文章:

更多优秀文章,长按下方二维码,“关注看雪学院公众号”查看!

    商务合作:wsc@kanxue.com

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存